/*******************************************************************************
 * Copyright (c) 2012, 2013 XML-Print Project.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     XML-Print Project - initial API and implementation
 ******************************************************************************/
package info.xmlprint.run;

import info.xmlprint.core.format.Format;
import info.xmlprint.core.format.FormatGroup;
import info.xmlprint.core.innerlink.InnerLink;
import info.xmlprint.core.innerlink.InnerLinkGroup;
import info.xmlprint.core.mapping.Mapping;
import info.xmlprint.core.mapping.MappingGroup;
import info.xmlprint.core.project.PrintProject;
import info.xmlprint.core.referencesystem.PageMaster;
import info.xmlprint.core.referencesystem.PageMasterGroup;
import info.xmlprint.core.referencesystem.ReferenceFieldGroup;
import info.xmlprint.core.referencesystem.ReferencingSystemGroup;
import info.xmlprint.core.xml.PElementGroup;
import info.xmlprint.core.xml.XMLScanner;
import info.xmlprint.font.FontManager;
import info.xmlprint.ui.message.GuiMessages;
import info.xmlprint.util.FileUtil;
import info.xmlprint.util.XmlUtil;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourceAttributes;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.xml.sax.InputSource;

public class RuntimeUtil {

	private static Logger logger = Logger
			.getLogger(RuntimeUtil.class.getName());

	private static RuntimeUtil runtimeObj;

	private PrintProject currentPrintProject;

	final private String SETTING_ROOT_ELEMENT = "setting";
	//final private String CONFIGURATION_ROOT_ELEMENT = "configuration";
	final private String CONFIGURATION_XMLNS = "http://xmlprint.eu/xml/config-schema";

	private HashMap<IProject, PrintProject> projectMap = new HashMap<IProject, PrintProject>();

	public static RuntimeUtil getInstance() {
		if (runtimeObj == null) {
			synchronized (RuntimeUtil.class) {
				if (runtimeObj == null) {
					runtimeObj = new RuntimeUtil();
				}
			}
		}
		return runtimeObj;
	}

	private RuntimeUtil() {
	}

	/**
	 * @ return inputstream of editor or xml file
	 */
	// public Object getContentResource(PrintProject printProject) {
	// if (printProject == null) {
	// return null;
	// }
	//
	// List<IFile> xmls = getContentXmls(printProject);
	// if (xmls == null) {
	// return null;
	// }
	// IFile xml = xmls.get(0);
	//
	// IWorkbenchPage page = PlatformUI.getWorkbench()
	// .getActiveWorkbenchWindow().getActivePage();
	// IEditorPart editorPart = page.findEditor(new FileEditorInput(xml));
	// if (editorPart != null) {
	// return editorPart;
	// } else {
	// return xml;
	// }
	// }

	/**
	 * Now we use only first xml file under directory "content", Todo is merge
	 * all xml files into one xml.
	 * 
	 * @return InputStream of top xml file, if file is open and not saved, then
	 *         return Strnig from editor
	 */
	public Object getContentSource(PrintProject printProject) {
		if (printProject == null) {
			return null;
		}

		List<IFile> xmls = getContentXmls(printProject);
		if (xmls == null) {
			return null;
		}
		IFile xml = xmls.get(0);

		IWorkbenchPage page = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage();
		IEditorPart editorPart = page.findEditor(new FileEditorInput(xml));
		if (editorPart != null) {
			// read from editor
			String out = WorkbenchInfo.getEditorText(editorPart);
			return out;
		} else {
			try {// read from file
				InputStream is = new BufferedInputStream(xml.getContents());
				return is;
			} catch (CoreException e) {
				logger.error(e);
				e.printStackTrace();
			}
		}
		return null;

	}

	/**
	 * 
	 */
	public String getContentText(PrintProject printProject) {
		if (printProject == null) {
			return null;
		}

		String out = "";//$NON-NLS-1$
		Object obj = getContentSource(printProject);
		if (obj == null) {
			return "";
		}
		if (obj instanceof String) {
			return WorkbenchInfo.transformSpecialChars((String) obj);
		} else if (obj instanceof InputStream) {
			InputStream is = (InputStream) obj;
			StringWriter writer = new StringWriter();
			try {
				IOUtils.copy(is, writer, "utf-8");//$NON-NLS-1$
				out = writer.toString();
				out = WorkbenchInfo.transformSpecialChars(out);
				is.close();
			} catch (IOException e) {
				logger.error(e);
				e.printStackTrace();
			}
		}
		return out;
	}

	/**
	 * @return all xmlFile under folder "content"
	 */
	public List<IFile> getContentXmls(PrintProject printProject) {
		if (printProject == null) {
			return null;
		}
		IProject project = printProject.getProject();
		if (project == null) {
			return null;
		}

		String folderName = Constants.CONTENT_INPUT_FOLDER;
		IFolder contentFolder = project.getFolder(new Path(folderName));
		if (!contentFolder.exists()) {
			FileUtil.createFolder(project, new String[] { folderName });
			return null;
		}

		List<IFile> list = new ArrayList<IFile>();
		try {
			IResource[] resources = contentFolder.members();
			for (IResource f : resources) {
				if (f instanceof IFile
						&& f.getFileExtension().equalsIgnoreCase("xml")) {//$NON-NLS-1$
					list.add((IFile) f);
				}
			}
		} catch (CoreException e) {
			e.printStackTrace();
		}

		if (list.size() < 1) {
			return null;
		}

		return list;
	}

	public PrintProject getCurrentPrintProject() {
		if (currentPrintProject == null
				|| currentPrintProject.getProject() == null
				|| !currentPrintProject.getProject().isOpen()) {
			IEditorPart editorPart = PlatformUI.getWorkbench()
					.getActiveWorkbenchWindow().getActivePage()
					.getActiveEditor();

			if (editorPart != null) {
				// if editor is open, set as current PrintProject
				IFileEditorInput input = (IFileEditorInput) editorPart
						.getEditorInput();
				IFile file = input.getFile();
				if (file.getParent().getName()
						.equalsIgnoreCase(Constants.CONTENT_INPUT_FOLDER)) {
					IProject project = file.getProject();
					if (currentPrintProject == null) {
						currentPrintProject = new PrintProject(project);
					} else
						currentPrintProject.setProject(project);
					return currentPrintProject;
				}
			}

			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
			IProject[] prjs = root.getProjects();
			for (IProject prj : prjs) {
				if (prj.isOpen()) {
					if (currentPrintProject == null) {
						currentPrintProject = new PrintProject(prj);
					}
					currentPrintProject.setProject(prj);
					return currentPrintProject;
				}
			}

			String str;
			if (prjs != null && prjs.length > 0) {
				str = "please select a project.";
			} else {
				str = "please create a new project";
				if (currentPrintProject != null) {
					projectMap.remove(currentPrintProject);
					currentPrintProject = null;
				}
			}
			MessageDialog.openInformation(null, "", str);

		}
		return currentPrintProject;
	}

	/*
	 * Standard- und Ignore-Formate
	 */
	public FormatGroup getDefaultFormatGroup() {
		FormatGroup fg = new FormatGroup();
		Format f1 = new Format(GuiMessages.Stileditor_ParagraphFormat_Standard);
		f1.setType("standard");//$NON-NLS-1$
		if (Configuration.isOs("windows"))
			f1.setAttribute("font-family", "Arial");//$NON-NLS-1$
		// else if (Configuration.isOs("linux"))
		// f1.setAttribute("font-family", "DejaVu Serif");
		else
			f1.setAttribute("font-family", "");//$NON-NLS-1$
		f1.setAttribute("font-size", "12.0pt");//$NON-NLS-1$
		f1.setAttribute("font-stretch", "normal");//$NON-NLS-1$
		f1.setAttribute("font-style", "normal");//$NON-NLS-1$
		f1.setAttribute("font-weight", "400");//$NON-NLS-1$
		f1.setAttribute("font-variant", "normal");//$NON-NLS-1$
		f1.setAttribute("letter-spacing", "normal");//$NON-NLS-1$
		f1.setAttribute("line-height", "1.2");//$NON-NLS-1$
		f1.setAttribute("margin-left", "0cm");//$NON-NLS-1$
		f1.setAttribute("margin-right", "0cm");//$NON-NLS-1$
		f1.setAttribute("space-after.maximum", "");//$NON-NLS-1$
		f1.setAttribute("space-after.minimum", "");//$NON-NLS-1$
		f1.setAttribute("space-after.optimum", "0cm");//$NON-NLS-1$
		f1.setAttribute("space-before.maximum", "");//$NON-NLS-1$
		f1.setAttribute("space-before.minimum", "");//$NON-NLS-1$
		f1.setAttribute("space-before.optimum", "0cm");//$NON-NLS-1$
		f1.setAttribute("text-align", "justify");//$NON-NLS-1$
		f1.setAttribute("text-indent", "0cm");//$NON-NLS-1$
		f1.setAttribute("text-decoration", "none");//$NON-NLS-1$
		f1.setAttribute("color", "black");//$NON-NLS-1$
		f1.setAttribute("text-addition-before", "");//$NON-NLS-1$
		f1.setAttribute("text-addition-after", "");//$NON-NLS-1$
		f1.setAttribute("baseline-shift", "baseline");//$NON-NLS-1$
		f1.setAttribute("language", "de");//$NON-NLS-1$
		f1.setAttribute("country", "DR");//$NON-NLS-1$
		f1.setAttribute("hyphenation-remain-character-count", "2");//$NON-NLS-1$
		f1.setAttribute("hyphenation-push-character-count", "2");//$NON-NLS-1$
		f1.setAttribute("hyphenate", "true");//$NON-NLS-1$
		f1.setAttribute("hyphenation-character", "-");//$NON-NLS-1$
		f1.setAttribute("column-count", "1");//$NON-NLS-1$
		f1.setAttribute("column-gaps", "");//$NON-NLS-1$
		f1.setAttribute("column-directions", "inherit");//$NON-NLS-1$
		f1.setAttribute("column-widths", "auto");//$NON-NLS-1$
		f1.setAttribute("break-after", "auto");//$NON-NLS-1$
		f1.setAttribute("pagemaster", "Standard");

		Format f2 = new Format(GuiMessages.Stileditor_ParagraphFormat_Ignore);
		f2.setType("ignore");//$NON-NLS-1$

		fg.addFormat(f1);
		fg.addFormat(f2);

		return fg;
	}

	public PageMasterGroup getDefaultPageMasterGroup() {
		PageMasterGroup pmg = new PageMasterGroup();
		PageMaster p1 = new PageMaster();

		p1.setAttribute("name", RuntimeUtil.getInstance()
				.getCurrentPrintProject().getFormatGroup().getStandardFormat()
				.getName());
		p1.setAttribute("page-layout-width", Constants.A4_W);
		p1.setAttribute("page-layout-height", Constants.A4_H);
		p1.setAttribute("page-layout-margin-top", Constants.margin_T);
		p1.setAttribute("page-layout-margin-bottom", Constants.margin_B);
		p1.setAttribute("page-layout-margin-left", Constants.margin_L);
		p1.setAttribute("page-layout-margin-right", Constants.margin_R);
		p1.setAttribute("page-layout-doublesided",
				Constants.doublesided.toString());

		pmg.add(p1);

		return pmg;
	}

	/*
	 * Ein XmlPathGroup von aktuellem Xml erzeugen, bei jedem oeffnen des
	 * Stileditor durchfuehren
	 */
	public XMLScanner getDefaultXMLScanner(PrintProject printProject) {
		if (printProject == null) {
			return null;
		}

		Object obj = getContentSource(printProject);
		if (obj == null)
			return null;

		Reader reader = null;
		InputSource inputSource = null;
		InputStream is = null;
		// wenn utf-8
		try {
			if (obj instanceof InputStream) {
				is = (InputStream) obj;
			} else if (obj instanceof String) {
				// TODO: use InputStream from editor? have to use
				// transformSpecialChars?
				is = new BufferedInputStream(new ByteArrayInputStream(
						((String) obj).getBytes()));

			}
			if (is == null) {
				return null;
			}
			reader = new InputStreamReader(is, "UTF-8");//$NON-NLS-1$
		} catch (IOException e) {
			logger.error("", e);
			e.printStackTrace();
		}
		inputSource = new InputSource(reader);
		inputSource.setEncoding("UTF-8");//$NON-NLS-1$ 

		XMLScanner pm = new XMLScanner();
		pm.setInput(inputSource);
		return pm;
	}

	private IFile getSettingXml(IProject project) {
		IFile xml = project.getFile(new Path(Constants.SETTING_OUTPUT_FOLDER
				+ System.getProperty("file.separator")
				+ Constants.SETTING_OUTPUT_FILE));//$NON-NLS-1$
		return xml;
	}

	/*
	 * Ein PElementGroup von aktuellem Xml erzeugen
	 */
	private PElementGroup getPElementGroup(InputStream is) {
		// XmlMap erzeugen
		Reader reader = null;

		// wenn utf-8
		try {
			reader = new InputStreamReader(is, "UTF-8");//$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			logger.error("", e);
			e.printStackTrace();
		}

		InputSource inputSource = new InputSource(reader);
		inputSource.setEncoding("UTF-8");//$NON-NLS-1$

		PElementGroup peg = new PElementGroup();
		peg.read(inputSource);
		try {
			reader.close();
			is.close();
		} catch (IOException e) {
			logger.error(e);
			e.printStackTrace();
		}
		return peg;
	}

	/*
	 * return PElementGrou of current project
	 */
	public PElementGroup getPElementGroup(PrintProject printProject) {
		// TODO: Mehrere XML unter "content" muessen alle geladen werden.
		if (printProject == null) {
			return null;
		}

		List<IFile> xmls = getContentXmls(printProject);
		if (xmls == null) {
			return null;
		}
		IWorkbenchPage page = PlatformUI.getWorkbench()
				.getActiveWorkbenchWindow().getActivePage();

		List<PElementGroup> pList = new ArrayList<PElementGroup>();

		try {
			// find all xml of content
			IEditorInput input;
			IEditorPart editorPart;
			InputStream is;
			for (IFile xml : xmls) {
				input = new FileEditorInput(xml);
				editorPart = page.findEditor(input);
				if (editorPart != null) {
					// read from editor
					is = new BufferedInputStream(new ByteArrayInputStream(
							WorkbenchInfo.getEditorText(editorPart).getBytes()));
				} else {
					// read from file
					is = new BufferedInputStream(xml.getContents());

				}
				if (is != null) {
					PElementGroup pg = getPElementGroup(is);
					pList.add(pg);
				}
			}

			// TODO: zum Test nur ein xml, muss noch weitere XML laden
			return pList.get(0);

		} catch (CoreException e) {
			e.printStackTrace();
		}

		return null;
	}

	public PrintProject getPrintProject(IProject project) {
		return projectMap.get(project);
	}

	public boolean isFormatInUse(String name, PrintProject project) {
		MappingGroup group = project.getMappingGroup();
		String s;
		for (Mapping mp : group.getList()) {
			s = mp.getFormatName();
			if (s.equals(name)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * read MappingGroup.xml unter /mapping/, mappingGroup, formatGroup,
	 * referenceSystemGroup and referenceFieldGroup will be defined
	 */
	public boolean readSettingXml(PrintProject pProject) {
		if (pProject == null) {
			return false;
		}

		IProject project = pProject.getProject();
		if (project == null) {
			return false;
		}
		IFile xml = getSettingXml(project);

		if (xml.exists()) {
			try {

				InputStream in = xml.getContents();
				Document doc = XmlUtil.getDocByStream(in);
				in.close();
				if (doc == null) {
					return false;
				}
				MappingGroup group = new MappingGroup();
				ReferencingSystemGroup rsg = new ReferencingSystemGroup();
				ReferenceFieldGroup rfg = new ReferenceFieldGroup();
				PageMasterGroup pmg = new PageMasterGroup();
				InnerLinkGroup ilg = new InnerLinkGroup();
				
				//read namespace
				Node namespace = doc.getRootElement().selectSingleNode(
						"//namespace");
				if (namespace != null) {
					Element e=(Element) namespace;
					String prefix=e.attributeValue("prefix");
					String uri=e.attributeValue("uri");
					if(prefix!=null && uri!=null && !prefix.isEmpty() && !prefix.isEmpty()){
						group.setMainNamespacePrefix(prefix);
						group.setMainNamespaceUri(uri);
					}
				}

				// read mappingGroup, formatGroup
				if (group.readXmlDoc(doc)) {
					pProject.setMappingGroup(group);
					FormatGroup formatGroup = group.getFormatGroup();
					pProject.setFormatGroup(formatGroup);
					
					
				}
				// read reference
				{
					rsg.updateByDoc(doc);
					rfg.updateByDoc(doc);
					pProject.setReferenceFieldGroup(rfg);
					pProject.setReferencingSystemGroup(rsg);
				}

				// read PageMasterGroup
				{
					pmg.updateByDoc(doc);
					pProject.setPageMasterGroup(pmg);
				}

				// read innerlinkGroup
				{
					Node n = doc.getRootElement().selectSingleNode(
							"//innerlinks");
					if (n != null) {
						ilg.updateByElement(n, InnerLink.ROOT_ELEMENT);
						pProject.setInnerLinkGroup(ilg);
					}
				}
				return true;
			} catch (CoreException | IOException e) {
				logger.error(e);
				e.printStackTrace();
			}
		}
		return false;
	}

	public void refreshFo(PrintProject pProject, String foPath) {
		if (pProject == null || foPath == null) {
			return;
		}

		IProject project = pProject.getProject();
		if (project == null) {
			return;
		}
		InputStream is = null;
		try {
			is = new FileInputStream(foPath);
		} catch (FileNotFoundException e) {
			logger.error(e);
			e.printStackTrace();
		}

		if (is != null) {
			writeXml(pProject, Constants.FO_OUTPUT_FOLDER,
					Constants.FO_OUTPUT_FILE, is);
		}
	}

	public void register(PrintProject printProject) {
		projectMap.put(printProject.getProject(), printProject);
	}

	public void setCurrentPrintProject(PrintProject printProject) {
		this.currentPrintProject = printProject;
	}

	/*
	 * 
	 */
	public boolean writeSettingXml(PrintProject pProject) {
		if (pProject == null) {
			return false;
		}
		List<Document> list = new ArrayList<Document>();

		// 1 add MappingGroup
		MappingGroup mg = pProject.getMappingGroup();
		if (mg == null) {
			return false;
		}
		list.add(mg.getXmlDoc());

		// 2 add FormatGroup
		FormatGroup fg = mg.getFormatGroup();
		if (fg == null) {
			return false;
		}
		list.add(fg.getXmlDoc());

		// 3 add ReferencingSystemGroup
		ReferencingSystemGroup rsg = pProject.getReferencingSystemGroup();
		if (rsg == null) {
			return false;
		}
		list.add(rsg.getDoc());

		// 4 add ReferenceFieldGroup
		ReferenceFieldGroup rfg = pProject.getReferenceFieldGroup();
		if (rfg == null) {
			return false;
		}
		list.add(rfg.getDoc());

		// 5 add PageMasterGroup
		PageMasterGroup pmg = pProject.getPageMasterGroup();
		if (pmg == null) {
			return false;
		}
		list.add(pmg.getDoc());

		// 6 add PageMasterGroup
		InnerLinkGroup ilg = pProject.getInnerLinkGroup();
		if (ilg == null) {
			return false;
		}
		list.add(ilg.getXmlDoc());

		Document settingDoc = DocumentHelper.createDocument();
		Element root = settingDoc.addElement(SETTING_ROOT_ELEMENT);
		for (Document d : list) {
			root.add(d.getRootElement());
		}

		String namespacePrefix = mg.getMainNamespacePrefix();
		String namespaceUri = mg.getMainNamespaceUri();
		if (namespacePrefix != null && namespaceUri != null) {
			// add Namespace
			Element e = root.addElement("namespace");
			e.addAttribute("prefix", namespacePrefix);
			e.addAttribute("uri", namespaceUri);
		}

		writeXml(pProject, Constants.SETTING_OUTPUT_FOLDER,
				Constants.SETTING_OUTPUT_FILE,
				XmlUtil.getStreamByDoc(settingDoc));
		return true;
	}

	public boolean writeConfigurationXml(PrintProject pProject) {
		if (pProject == null) {
			return false;
		}
		Document configDoc = DocumentHelper.createDocument();
		Element root = configDoc.addElement(SETTING_ROOT_ELEMENT);
		root.addAttribute("xmlns", CONFIGURATION_XMLNS);

		// add <fonts> and its children
		Document fontDoc = FontManager.getInstance().getUsedFontXmlDoc(pProject.getMappingGroup());
		if (fontDoc != null) {
			root.add(fontDoc.getRootElement());
		}
		// add <debug show-debug-output="true"/>
		root.addElement("debug").addAttribute("show-debug-output", "true");

		// add <footnotes> an its children
		writeXml(pProject, Constants.SETTING_OUTPUT_FOLDER,
				Constants.CONFIGURATION_FILE, XmlUtil.getStreamByDoc(configDoc));

		return true;
	}

	private void writeXml(PrintProject pProject, String folderName,
			String fName, InputStream in) {
		if (pProject == null || in == null) {
			return;
		}
		IProject project = pProject.getProject();
		if (project == null) {
			return;
		}
		IFolder dir = project.getFolder(new Path(folderName));
		if (!dir.exists()) {
			if (!FileUtil.createFolder(project, new String[] { folderName })) {
				return;
			}
		}

		IFile file = dir.getFile(new Path(fName));

		try {
			ResourceAttributes res = new ResourceAttributes();
			if (!file.exists()) {
				res.setReadOnly(true);
				file.create(in, true, null);
				file.setResourceAttributes(res);
			} else {
				res.setReadOnly(false);
				file.setResourceAttributes(res);
				file.setContents(in, true, false, null);
				res.setReadOnly(true);
				file.setResourceAttributes(res);
			}
			in.close();

			// kann die Datei verbinden aber es wird kein XML-Struktur(Design
			// Ansicht) angezeigt
			// if(folderName.equals("fo")){
			// IDE.setDefaultEditor(file,
			// "org.eclipse.wst.xml.ui.internal.tabletree.XMLMultiPageEditorPart");
			// }
		} catch (UnsupportedEncodingException e) {
			logger.error(e);
			e.printStackTrace();
		} catch (CoreException e) {
			logger.error(e);
			e.printStackTrace();
		} catch (IOException e) {
			logger.error(e);
			e.printStackTrace();
		}
	}

}
